06 异常处理
程序运行时总会出错:文件不存在、网络断开、用户输入了非法数据。如果不处理,程序直接崩溃。try/except让你捕获并处理异常,raise让你主动抛出异常。
一、try/except
1.1 基本用法
python
>>> while True:
... try:
... x = int(input("请输入一个数字: "))
... break
... except ValueError:
... print("无效数字,请重试!")try里的代码如果发生ValueError,就跳到except执行,不会崩溃。
1.2 捕获多种异常
python
>>> try:
... # 可能出错的代码
... result = 1 / 0
... except ZeroDivisionError:
... print("除以零")
... except (ValueError, TypeError):
... print("值错误或类型错误")一个try可以跟多个except,分别处理不同类型的异常。也可以用元组捕获多种异常。
1.3 捕获异常信息
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError as e:
... print(f"错误: {e}")
...
错误: division by zero二、else子句
else在try没有发生异常时执行:
python
>>> try:
... f = open('workfile', encoding='utf-8')
... except OSError:
... print("无法打开文件")
... else:
... content = f.read()
... f.close()
... print(f"文件有{len(content)}个字符")else的好处是:只把可能出错的代码放在try里,正常逻辑放在else里,避免意外捕获不需要的异常。
三、finally子句
finally不管有没有异常都会执行,通常用于清理资源:
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError:
... print("除以零")
... finally:
... print("这段总是会执行")完整的try语句:try → except → else → finally。
四、raise抛出异常
4.1 基本用法
python
>>> def divide(a, b):
... if b == 0:
... raise ValueError("除数不能为零")
... return a / b
...
>>> divide(1, 0)
ValueError: 除数不能为零4.2 重新抛出
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError:
... print("记录错误日志")
... raise # 重新抛出当前异常raise不带参数会重新抛出当前正在处理的异常。
4.3 异常链
python
>>> def func():
... try:
... result = 1 / 0
... except ZeroDivisionError as e:
... raise RuntimeError("计算失败") from e
...
>>> func()
Traceback:
...
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
...
RuntimeError: 计算失败from指定原始异常,形成异常链,方便调试。
五、自定义异常
5.1 定义异常类
python
>>> class MyError(Exception):
... pass
...
>>> raise MyError("出错了")
MyError: 出错了异常类通常继承Exception,可以添加属性:
python
>>> class ValidationError(Exception):
... def __init__(self, field, message):
... self.field = field
... self.message = message
... super().__init__(f"{field}: {message}")
...
>>> raise ValidationError("email", "格式不正确")
ValidationError: email: 格式不正确5.2 异常层次
BaseException
+-- KeyboardInterrupt
+-- SystemExit
+-- Exception
+-- ValueError
+-- TypeError
+-- RuntimeError
+-- OSError
| +-- FileNotFoundError
| +-- PermissionError
+-- 自定义异常自定义异常应该继承Exception,不要继承BaseException。
六、ExceptionGroup(3.11+)
6.1 基本概念
ExceptionGroup可以同时抛出多个异常:
python
>>> def collect_errors():
... errors = []
... try:
... int("abc")
... except ValueError as e:
... errors.append(e)
... try:
... 1 / 0
... except ZeroDivisionError as e:
... errors.append(e)
... if errors:
... raise ExceptionGroup("多个错误", errors)
...
>>> collect_errors()
+ Exception Group Traceback (most recent call last):
| ...
| ExceptionGroup: 多个错误 (2 sub-exceptions)
+-+---------------- 1 ----------------
| ValueError: invalid literal for int() with base 10: 'abc'
+---------------- 2 ----------------
| ZeroDivisionError: division by zero
+------------------------------------6.2 except*匹配
except*匹配ExceptionGroup中的特定异常:
python
>>> try:
... raise ExceptionGroup("错误", [
... ValueError("无效值"),
... TypeError("类型错误"),
... FileNotFoundError("文件不存在"),
... ])
... except* ValueError as eg:
... print(f"值错误: {eg.exceptions}")
... except* (TypeError, FileNotFoundError) as eg:
... print(f"其他错误: {eg.exceptions}")except*可以匹配多次,每次处理一个子组。
七、实用模式
7.1 忽略异常
python
>>> try:
... value = config["key"]
... except KeyError:
... value = default_value
>>> # 或者用dict.get(),但try/except更通用7.2 清理资源
python
>>> f = open('workfile', 'w')
>>> try:
... f.write('data')
... # ... 更多操作
... finally:
... f.close() # 无论是否出错都关闭或者更推荐用with:
python
>>> with open('workfile', 'w') as f:
... f.write('data')7.3 断言(assert)
python
>>> def calculate_average(numbers):
... assert len(numbers) > 0, "列表不能为空"
... return sum(numbers) / len(numbers)assert用于开发时检查不变量,不要用于处理用户输入。-O参数会禁用assert。
八、总结
| 语句 | 用途 |
|---|---|
try/except | 捕获异常 |
try/except/else | 没异常时执行else |
try/finally | 总是执行清理代码 |
raise | 抛出异常 |
raise ... from ... | 异常链 |
ExceptionGroup | 同时抛出多个异常(3.11+) |
except* | 匹配异常组(3.11+) |
assert | 开发时检查条件 |
with语句是处理文件等资源的最佳方式,比try/finally更简洁。自定义异常继承Exception,不要继承BaseException。